package com.acheli.rideable.factory.gson;

import com.acheli.ACHeli;
import com.acheli.registry.config.Config;
import com.acheli.registry.config.CustomDataBus;
import com.acheli.registry.config.GsonFactoryConfig;
import com.acheli.registry.event.Debugging;
import com.acheli.registry.keybind.KeyBinding;
import com.acheli.registry.network.ACHMessages;
import com.acheli.registry.network.ACHeliPacketType;
import com.acheli.registry.network.packet.CustomSyncableC2SPacket;
import com.acheli.registry.resource.DynamicResourcePack;
import com.acheli.rideable.entites.RideableEntity;
import com.acheli.rideable.factory.ACHeliEntitiesFactory;
import com.acheli.rideable.functionLathes.ComponentFL;
import com.acheli.rideable.functionLathes.EntityFLProvider;
import com.acheli.rideable.functionLathes.RideableFL;
import com.acheli.rideable.functionLathes.component.DefaultComponentProvider;
import com.acheli.rideable.functionLathes.component.PosShower;
import com.acheli.rideable.functionLathes.component.compile.EventToJS;
import com.acheli.rideable.functionLathes.component.compile.Invoke;
import com.acheli.rideable.functionLathes.component.compile.JSUtils;
import com.acheli.utils.rotate.EntityDirectionUtils;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.registries.ForgeRegistries;
import org.joml.Quaternionf;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import java.util.List;
import java.util.Objects;

public class GsonEntitiesFactory {
    public static class GsonRideable extends ACHeliEntitiesFactory<RideableEntity, RideableFL> {
        public GsonFactoryConfig config;
        public GsonRideable(RideableEntity entity, GsonFactoryConfig config) {
            super(entity, new GsonFunction(config));
            this.config = config;
        }

        private static class GsonFunction extends EntityFLProvider<RideableEntity, RideableFL> {
            private final GsonFactoryConfig config;
            private final Context JS_RUNNER;
            private final Scriptable JS_SCOPE;
            private boolean jsLocked = true;
            private final PosShower posShower = new PosShower("pos", cachedEntity, Vec3.ZERO);
            public GsonFunction(GsonFactoryConfig config) {
                super(new RideableFL());
                this.config = config;
                JS_RUNNER = Context.enter();// 初始化 Rhino 上下文
                JS_SCOPE = JS_RUNNER.initStandardObjects();
            }

            @Override
            public void INIT() {
                super.INIT();
                fl.addComponent(posShower);
                if (cachedEntity.level().isClientSide) {
                    List<DefaultComponentProvider> providerList = CustomDataBus.gsonDefaultComponentProvidersMap.get(cachedEntity.getFactoryName());
                    if (providerList != null) {
                        for (DefaultComponentProvider componentProvider : providerList) {
                            ComponentFL componentFL = componentProvider.get(cachedEntity);
                            fl.addComponent(componentFL);
                            System.out.println("ADDED");
                            System.out.println(componentFL);

                        }
                        jsLocked = false;
                    }
                } else {
                    jsLocked = false;
                }


            }

            public void physics() {
                fl.rideablePhysicsObject.setMass(fl.getMass());
            }

            @Override
            public void interact(Player player, InteractionHand hand) {
                super.interact(player, hand);
                runScript(new EventToJS("interact", player, hand));
                System.out.println(config.vehicle_name);
            }

            @Override
            public void tick() {
                super.tick();
                boolean isClientSide = cachedEntity.level().isClientSide;
                runScript(new EventToJS("tick", isClientSide ? "client" : "server"));
                Vec3 testPos = new Vec3(Debugging.testing[0], Debugging.testing[1], Debugging.testing[2]);
                if (!Objects.equals(posShower.getRelativePosition(), testPos)) {
                    if (isClientSide) {
                        Minecraft.getInstance().player.sendSystemMessage(Component.literal("testPos: "+testPos));
                    }
                    posShower.setRelativePosition(testPos);
                }

                if (isClientSide) {
//                    cachedEntity.move(MoverType.SELF, cachedEntity.getDeltaMovement());

                }


            }

            @Override
            public CompoundTag serverReceiveAndSending(CompoundTag serverOriginNbt, ServerPlayer executingPlayer, ACHeliPacketType packetType) {
                runScript(new EventToJS("receive", "server", serverOriginNbt, executingPlayer, packetType));
//                if (packetType == ACHeliPacketType.Active) {
//                    cachedEntity.setPos(
//                            serverOriginNbt.getDouble("x"),
//                            serverOriginNbt.getDouble("y"),
//                            serverOriginNbt.getDouble("z")
//                    );
//                }
                return super.serverReceiveAndSending(serverOriginNbt, executingPlayer, packetType);
            }

            @Override
            public void clientReceive(CompoundTag receivedNbt, boolean fromSelf, ACHeliPacketType packetType) {
                runScript(new EventToJS("receive", "client", receivedNbt, fromSelf, packetType));
                super.clientReceive(receivedNbt, fromSelf, packetType);
            }

            @Override
            public void render(EntityRendererProvider.Context context, RideableEntity entity, float yaw, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight) {
                super.render(context, entity, yaw, partialTick, poseStack, buffer, packedLight);
                fl.applyRotation(poseStack);
                ItemRenderer itemRenderer = context.getItemRenderer();
                config.item_models.forEach((k, v) -> {
                    ItemStack itemStack = new ItemStack(ForgeRegistries.ITEMS.getValue(ResourceLocation.tryBuild(k, v)));
                    itemRenderer.renderStatic(itemStack, ItemDisplayContext.GROUND, packedLight, OverlayTexture.NO_OVERLAY, poseStack, buffer, entity.level(), 0);

                });
            }

            @Override
            public void renderTick(PoseStack matrices, float tickDelta) {
                super.renderTick(matrices, tickDelta);
                RideableFL fl = this.fl;
                Quaternionf target = fl.totalRotation;
                target.rotateY((float) fl.totalYMomentum);
                target.rotateX((float) fl.totalXMomentum);
                target.rotateZ((float) fl.totalZMomentum);
                fl.totalRotation.slerp(target, 0.1f * tickDelta);
                fl.totalYMomentum *=  0.998;
                fl.totalXMomentum *=  0.998;
                fl.totalZMomentum *=  0.998;

                fl.rideablePhysicsObject.setCurrentPos(cachedEntity.position());
                fl.rideablePhysicsObject.setCurrentForce(fl.getForce());
                fl.rideablePhysicsObject.setCurrentVelocity(cachedEntity.getDeltaMovement());
//                fl.rideablePhysicsObject.physicsObjTick((float) 30 /Minecraft.getInstance().getFps());
                fl.rideablePhysicsObject.physicsObjTick(tickDelta);

                Vec3 velocity = fl.rideablePhysicsObject.getCurrentVelocity(cachedEntity.getDeltaMovement());
                if (! fl.rideablePhysicsObject.getDisabled()) {
                    cachedEntity.setDeltaMovement(velocity);
                }
            }

            @Override
            public void runScript(EventToJS event) {
                boolean jsMode = Config.jsDebugMode;
                if (jsLocked && jsMode) {
                    ACHeli.LOGGER.error("JS Locked");
                    return;
                }

                try {
                    for (String PACKNAME :  ACHeli.GsonEntitiesConfigRegisterMap.keySet().stream().toList()) {
                        GsonFactoryConfig factoryConfig = ACHeli.GsonEntitiesConfigRegisterMap.get(PACKNAME);
                        if (factoryConfig.equals(config)) {

                            // 注册到 JS 全局作用域

                            //对象注入
                            ScriptableObject.putProperty(JS_SCOPE, "mc", Context.javaToJS(new JSUtils(), JS_SCOPE,  JS_RUNNER));
                            ScriptableObject.putProperty(JS_SCOPE, "invoke", Context.javaToJS(new Invoke(), JS_SCOPE,  JS_RUNNER));
                            ScriptableObject.putProperty(JS_SCOPE, "direct", Context.javaToJS(new EntityDirectionUtils(), JS_SCOPE,  JS_RUNNER));
                            ScriptableObject.putProperty(JS_SCOPE, "event", Context.javaToJS(event, JS_SCOPE,  JS_RUNNER));
                            ScriptableObject.putProperty(JS_SCOPE, "fl", Context.javaToJS(fl, JS_SCOPE,  JS_RUNNER));
                            ScriptableObject.putProperty(JS_SCOPE, "func", Context.javaToJS(this, JS_SCOPE,  JS_RUNNER));
                            ScriptableObject.putProperty(JS_SCOPE, "rideable", Context.javaToJS(cachedEntity, JS_SCOPE,  JS_RUNNER));

                            //类注入
                            ScriptableObject.putProperty(JS_SCOPE, "ACHeliPacketType", Context.getCurrentContext().getWrapFactory().wrapAsJavaObject(
                                    JS_RUNNER, JS_SCOPE, ACHeliPacketType.class, ScriptRuntime.ClassClass
                            ));
                            ScriptableObject.putProperty(JS_SCOPE, "CompoundTag", Context.getCurrentContext().getWrapFactory().wrapAsJavaObject(
                                    JS_RUNNER, JS_SCOPE, CompoundTag.class, ScriptRuntime.ClassClass
                            ));
                            ScriptableObject.putProperty(JS_SCOPE, "Vec3", Context.getCurrentContext().getWrapFactory().wrapAsJavaObject(
                                    JS_RUNNER, JS_SCOPE, Vec3.class, ScriptRuntime.ClassClass
                            ));
                            ScriptableObject.putProperty(JS_SCOPE, "Quaternionf", Context.getCurrentContext().getWrapFactory().wrapAsJavaObject(
                                    JS_RUNNER, JS_SCOPE, Quaternionf.class, ScriptRuntime.ClassClass
                            ));

                            for (ResourceLocation location : CustomDataBus.dynamicResourcePackHashMap.keySet().stream().toList()) {
                                DynamicResourcePack pack = CustomDataBus.dynamicResourcePackHashMap.get(location);
                                if (location.getPath().startsWith(PACKNAME+"/"+"script")) {
                                    try {
                                        if (KeyBinding.TEST_CHANGE.isDown()) {
                                            pack.reload();
                                        }
                                        String js_content = pack.getContent();
//                                    System.out.println(js_content);
                                        Object jsObj = JS_RUNNER.evaluateString(
                                                JS_SCOPE, js_content,
                                                location.getPath(), 1, null);
                                    } catch (Exception e) {
                                        if (jsMode) {
                                            String msg = e.getMessage();
                                            if (cachedEntity.level().isClientSide) {
                                                Minecraft.getInstance().player.sendSystemMessage(Component.literal("JS Error:" + msg));
                                            } else {
                                                ACHeli.LOGGER.error("Server JS Error:" + msg);
                                            }
                                        }

                                    }
//                                    finally {
//                                        Context.exit();
//                                    }
                                }
                            }


                        }
                    }
                } catch (Exception e) {
                    if (jsMode) {
                        ACHeli.LOGGER.error("Unknown JS Running Error:" + e.getMessage());
                    }
                }
            }

        }

    }

}
